﻿using System;
using System.Reflection;
using System.Collections.Generic;
using MugenInjection.Activators;
using MugenInjection.Core.Components;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interception.Bindings.Builders;
using MugenInjection.Interception.Interface;
using MugenInjection.Interception.Interface.Components;
using MugenInjection.Interception.Proxy;
using MugenInjection.Interception.Syntax;
using MugenInjection.Interface;
using MugenInjection.Interface.Components;
using MugenInjection.Syntax;

namespace MugenInjection.Interception.Components
{
    /// <summary>
    /// Represents the component that manages IInterceptors.
    /// </summary>
    public class InterceptionManagerComponent : Component, IInterceptionManagerComponent
    {
        #region Nested type: InterceptorComparer

        private class InterceptorComparer : IComparer<IInterceptorProcess>
        {
            #region Implementation of IComparer<IInterceptorProcess>

            /// <summary>
            /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
            /// </summary>
            /// <returns>
            /// Value Condition Less than zero<paramref name="x"/> is less than <paramref name="y"/>.Zero<paramref name="x"/> equals <paramref name="y"/>.Greater than zero<paramref name="x"/> is greater than <paramref name="y"/>.
            /// </returns>
            /// <param name="x">The first object to compare.</param><param name="y">The second object to compare.</param>
            public int Compare(IInterceptorProcess x, IInterceptorProcess y)
            {
                return y.Priority.CompareTo(x.Priority);
            }

            #endregion
        }

        #endregion

        #region Fields

        internal const string TargetKey = "_________TargetKey__________";
        internal const string AlwaysNewProxyKey = "_________AlwaysNewProxyKey__________";
        internal const string ProxyCollectionFuncKey = "_________ProxyCollectionFuncKey__________";

        private readonly CacheCollection<IBinding, List<IInterceptorProcess>> _interceptors = new CacheCollection<IBinding, List<IInterceptorProcess>>();

        private static readonly IList<IInterceptorProcess> Empty = new IInterceptorProcess[0];
        private static volatile IProxyFactory _defaultProxyFactory;
        private static readonly InterceptorComparer Comparer = new InterceptorComparer();

        #endregion

        #region Property

        /// <summary>
        /// Gets the <see cref="IProxyFactory"/>.
        /// </summary>
        public virtual IProxyFactory ProxyFactory
        {
            get
            {
                if (_defaultProxyFactory == null)
                {
                    lock (Empty)
                    {
                        if (_defaultProxyFactory == null)
                            _defaultProxyFactory = new MugenInjectionProxyFactory();
                    }
                }
                return _defaultProxyFactory;
            }
        }

        #endregion

        #region Method

        /// <summary>
        /// Gets all <see cref="IInterceptorProcess"/> for specified <see cref="IBinding"/>.
        /// </summary>
        /// <param name="binding">The specified <see cref="IBinding"/>.</param>
        /// <returns>Found <see cref="IList{IInterceptorProcess}"/>.</returns>
        public virtual IList<IInterceptorProcess> GetInterceptorProcesses(IBinding binding)
        {
            Validate.ArgumentNotNull(binding, "binding");
            lock (_interceptors)
            {
                List<IInterceptorProcess> interceptorProcesses = _interceptors[binding];
                if (interceptorProcesses == null)
                    return Empty;
                return interceptorProcesses;
            }
        }

        /// <summary>
        /// Adds the specified <see cref="IInterceptorProcess"/> for specified <see cref="IBinding"/>.
        /// </summary>
        /// <param name="binding">The specified <see cref="IBinding"/>.</param>
        /// <param name="interceptorProcess">The specified <see cref="IInterceptorProcess"/>.</param>
        public virtual void AddInterceptorProcess(IBinding binding, IInterceptorProcess interceptorProcess)
        {
            Validate.ArgumentNotNull(binding, "binding");
            Validate.ArgumentNotNull(interceptorProcess, "interceptorProcess");
            lock (_interceptors)
            {
                binding.Disposed += BindingDisposed;
                List<IInterceptorProcess> interceptorProcesses = _interceptors.GetOrAdd(binding, () => new List<IInterceptorProcess>());
                interceptorProcesses.Add(interceptorProcess);
                interceptorProcesses.Sort(Comparer);
            }
        }

        /// <summary>
        /// Adds the specified <see cref="ISimpleInterceptorProcess"/> for specified <see cref="IBinding"/>.
        /// </summary>
        /// <param name="binding">The specified <see cref="IBinding"/>.</param>
        /// <param name="simpleInterceptorProcess">The specified <see cref="ISimpleInterceptorProcess"/>.</param>
        public virtual void AddInterceptorProcess(IBinding binding, ISimpleInterceptorProcess simpleInterceptorProcess)
        {
            AddInterceptorProcess(binding, new SimpleInterceptorProcessAdapter(simpleInterceptorProcess));
        }

        /// <summary>
        /// Indicates that instances associated with this binding will be proxied.
        /// </summary>
        /// <param name="bindingSyntax">The binding syntax target.</param>
        /// <param name="interfaces">The specified interfaces for proxy.</param>
        /// <param name="interceptorProcesses">The specified <see cref="IInterceptorProcess"/> for intercept.</param>
        /// <returns>The fluent syntax.</returns>
        public virtual IInterceptTypeSyntax Intercept(ICommonBindingSyntax bindingSyntax, Type[] interfaces, IInterceptorProcess[] interceptorProcesses)
        {
            return InterceptInternal<object>(bindingSyntax, interfaces, interceptorProcesses);
        }

        /// <summary>
        /// Indicates that instances associated with this binding will be proxied.
        /// </summary>
        /// <param name="genericBindingSyntax">The binding syntax target.</param>
        /// <param name="interfaces">The specified interfaces for proxy.</param>
        /// <param name="interceptorProcesses">The specified <see cref="IInterceptorProcess"/> for intercept.</param>
        /// <returns>The fluent syntax.</returns>
        public IInterceptTypeSyntax<T> Intercept<T>(IGenericBindingSyntax<T> genericBindingSyntax, Type[] interfaces, IInterceptorProcess[] interceptorProcesses)
        {
            return InterceptInternal<T>(genericBindingSyntax, interfaces, interceptorProcesses);
        }

        /// <summary>
        /// Indicates that instances associated with this binding will be proxied and the target will be to the original binding.
        /// </summary>
        /// <param name="bindingBuilder">The binding builder for target.</param>
        /// <param name="interfaces">Interfaces for proxy.</param>
        /// <param name="alwaysNewProxy">Always create a new proxy object, regardless of the target.</param>
        /// <param name="interceptorProcesses">The specified <see cref="IInterceptorProcess"/> for intercept.</param>
        public TSyntaxType InterceptAsTarget<TSyntaxType>(IBindingBuilder bindingBuilder, Type[] interfaces, bool alwaysNewProxy, IInterceptorProcess[] interceptorProcesses)
        {
            return InterceptAsTargetInternal<object, TSyntaxType>(bindingBuilder, interfaces, alwaysNewProxy, interceptorProcesses);
        }

        /// <summary>
        /// Indicates that instances associated with this binding will be proxied and the target will be to the original binding.
        /// </summary>
        /// <param name="bindingBuilder">The binding builder for target.</param>
        /// <param name="interfaces">Interfaces for proxy.</param>
        /// <param name="alwaysNewProxy">Always create a new proxy object, regardless of the target.</param>
        /// <param name="interceptorProcesses">The specified <see cref="IInterceptorProcess"/> for intercept.</param>
        public TSyntaxType InterceptAsTarget<TType, TSyntaxType>(IBindingBuilder bindingBuilder, Type[] interfaces, bool alwaysNewProxy, IInterceptorProcess[] interceptorProcesses)
        {
            return InterceptAsTargetInternal<TType, TSyntaxType>(bindingBuilder, interfaces, alwaysNewProxy, interceptorProcesses);
        }

        /// <summary>
        /// Creates an <see cref="IInterceptor"/> for intercept all processes.
        /// </summary>
        /// <param name="proxy">The specified <see cref="IProxy"/>. </param>
        /// <param name="interceptorProcesses">The specified <see cref="IInterceptorProcess"/> for intercept proxy.</param>
        /// <returns>An instance of the <see cref="IInterceptor"/>.</returns>
        public virtual IInterceptor CreateInterceptor(IProxy proxy, IList<IInterceptorProcess> interceptorProcesses)
        {
            var methodProxy = proxy as MethodProxy;
            if (methodProxy == null)
                throw new ArgumentException("An InterceptionManagerComponent works only with the type of proxy MethodProxy.");
            return new DefaultInterceptor(methodProxy) { Interceptors = interceptorProcesses };
        }

        #endregion

        #region Internal methods

        private static Type GetBaseProxyType(IEnumerable<Type> services, out List<Type> interfaces)
        {
            Type currentType = typeof(object);
            interfaces = new List<Type>();
            foreach (Type type in services)
            {
#if NETFX_CORE
                if (type.GetTypeInfo().IsInterface)
#else
                if (type.IsInterface)
#endif

                {
                    interfaces.Add(type);
                    continue;
                }
#if NETFX_CORE
                if (type.GetTypeInfo().IsSubclassOf(currentType))
#else
                if (type.IsSubclassOf(currentType))
#endif
                {
                    currentType = type;
                    continue;
                }
                if (type.IsAssignableFrom(currentType)) continue;
                throw new NotSupportedException(
                    @"It is impossible create a proxy class for different classes. 
The class must have a common parent, for example Alfa --> Bravo --> Omega, but not Alfa --> Bravo; Alfa --> Omega.");
            }
            return currentType;
        }

        private InterceptorBindingBuilder<T> InterceptInternal<T>(ICommonBindingSyntax bindingSyntax, IEnumerable<Type> interfaces, IList<IInterceptorProcess> interceptorProcesses)
        {
            Validate.ArgumentNotNull(bindingSyntax, "bindingSyntax");
            Validate.ArgumentNotNull(interfaces, "interfaces");
            Validate.ArgumentNotNull(interceptorProcesses, "interceptorProcesses");
            Type proxy;
            List<Type> intr;
            Type baseType = GetBaseProxyType(bindingSyntax.Services, out intr);
            intr.AddRange(interfaces);
            Func<IProxyCollection> proxyCollectionFunc = ProxyFactory.CreateProxy(baseType, intr.ToArray(), out proxy);
            var syntax = bindingSyntax.To(proxy);
            syntax.WithSetting(ProxyCollectionFuncKey, proxyCollectionFunc);
            return new InterceptorBindingBuilder<T>(syntax, interceptorProcesses);
        }

        private TSyntax InterceptAsTargetInternal<TType, TSyntax>(IBindingBuilder bindingBuilder, IEnumerable<Type> interfaces, bool alwaysNewProxy, IList<IInterceptorProcess> interceptorProcesses)
        {
            Validate.ArgumentNotNull(bindingBuilder, "bindingBuilder");
            Validate.ArgumentNotNull(interfaces, "interfaces");
            Validate.ArgumentNotNull(interceptorProcesses, "interceptorProcesses");
            List<Type> intr;
            Type baseType = GetBaseProxyType(bindingBuilder.Services, out intr);
            intr.AddRange(interfaces);
            Type proxy;
            var proxyCollectionFunc = ProxyFactory.CreateProxy(baseType, intr.ToArray(), out proxy);

            //Create the interceptor binding.
            var managerComponent = bindingBuilder.Injector.Components.BindingManagerComponent;
            IBinding binding = managerComponent.CreateSelfBindable(proxy, () => new InterceptorActivator(bindingBuilder.Injector.Settings.DefaultActivatorFactory()), bindingBuilder.Injector.Settings.DefaultConstructorResolverFactory, false);
            binding.Settings.Add(ProxyCollectionFuncKey, proxyCollectionFunc);
            if (alwaysNewProxy)
                binding.Settings.Add(AlwaysNewProxyKey, true);
            object interceptorBuilder = new InterceptorBindingBuilder<TType>(binding, bindingBuilder, interceptorProcesses);
            if (interceptorBuilder is TSyntax)
                return (TSyntax) interceptorBuilder;
            throw new NotSupportedException(
                string.Format("The type {0} is not supported by the InterceptionManagerComponent.", typeof (TSyntax)));
        } 

        #endregion

        #region Event

        private void BindingDisposed(IDisposableObject obj)
        {
            obj.Disposed -= BindingDisposed;
            var binding = (IBinding)obj;
            lock (_interceptors)
                _interceptors.Remove(binding);
        }

        #endregion

        #region Overrides of Component

        /// <summary>
        /// Indicates whether the component is unique.
        /// </summary>
        public override sealed bool IsSingle
        {
            get { return true; }
        }

        /// <summary>
        /// Gets the component type. Need to find a component in the container.
        /// </summary>
        public override sealed Type ComponentType
        {
            get { return typeof(IInterceptionManagerComponent); }
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override IComponent Clone()
        {
            return new InterceptionManagerComponent();
        }

        #endregion
    }
}